Skip to content

feat: add local video support#2970

Closed
diary112233 wants to merge 2 commits intoopen-ani:mainfrom
diary112233:my-feature
Closed

feat: add local video support#2970
diary112233 wants to merge 2 commits intoopen-ani:mainfrom
diary112233:my-feature

Conversation

@diary112233
Copy link
Copy Markdown

标题:feat: 支持为剧集绑定本地视频文件并持久化复用(桌面端)

变更概述
这个 PR 为剧集播放页增加了“绑定本地视频文件”的能力。
用户在桌面端为某一集手动选择本地视频后,应用会将该绑定关系持久化保存。之后再次进入同一条目/同一剧集时,这个本地文件会作为一个特殊的数据源参与现有的媒体选择与播放流程,用户不需要每次重新手动选择。
这个改动的目标是让“本地已有视频文件”的使用场景能够无缝接入现有播放流程,同时尽量不影响原有的在线数据源选择逻辑。

主要功能
支持为当前剧集手动绑定一个本地视频文件
支持清除当前剧集的本地文件绑定
绑定关系会持久化保存,重启应用后仍然有效
已绑定的本地文件会作为一个特殊媒体源出现在当前剧集的数据源选择流程中
在绑定完成后,会自动刷新媒体列表并优先选中对应的本地文件

实现方式

  1. 增加剧集与本地文件的持久化绑定存储
    新增了 EpisodeLocalFileBindingRepository,用于保存 (subjectId, episodeId) -> 本地文件路径 的映射关系。
    除了文件路径之外,还会在绑定时尽量复用当前已选媒体的一些属性信息,例如:字幕语言、分辨率、字幕组/联盟信息、字幕类型
    这些信息会一起保存,目的是让本地文件在后续作为媒体源参与选择时,仍然能尽量保留原有媒体的展示信息与筛选体验。
    绑定数据通过独立的 DataStore 条目持久化保存。

  2. 新增本地绑定媒体源
    新增了 LocalEpisodeFileBindingMediaSource,作为一个特殊媒体源接入现有媒体查询流程。
    这个媒体源在查询时会:
    根据当前 subjectIdepisodeId 查找是否存在本地绑定
    检查绑定的文件是否仍然存在、是否为普通文件
    如果有效,则构造一个 ResourceLocation.LocalFile 类型的媒体项返回
    如果文件不存在或无效,则返回空结果
    这样做的目的,是把本地文件纳入现有“查询 -> 选择 -> 播放”的统一流程,而不是额外维护一条完全独立的播放分支。

  3. 在依赖注入中注册绑定仓库与本地媒体源
    在 Koin 模块中注册了新的本地绑定仓库,并将本地绑定媒体源加入媒体源管理流程,使其能参与剧集页面现有的数据源选择逻辑。

  4. 在播放页 ViewModel 中接入绑定能力
    EpisodeViewModel 中增加了:
    当前剧集本地绑定状态的监听
    绑定本地文件的方法
    清除本地绑定的方法

绑定流程大致为:

  1. 校验用户选择的文件路径是否存在且为普通文件
  2. 取当前剧集信息,保存 (subjectId, episodeId) 对应的绑定关系
  3. 重启当前媒体查询
  4. 在刷新后的候选媒体中找到刚刚加入的本地媒体项
  5. 自动将其选为当前播放媒体

清除绑定时则会删除当前剧集的绑定记录,并重新触发媒体查询,使界面与候选列表状态保持一致。

  1. 在桌面端媒体选择侧栏提供入口
    在媒体选择侧栏中增加了桌面端入口,提供:
    使用本地视频
    重新绑定
    清除绑定
    这个入口当前只在桌面平台显示,不在移动端显示。

平台范围
这个功能当前面向桌面平台,UI 开关基于项目内的桌面平台抽象
按当前实现意图,桌面平台包括:
Windows
macOS
Linux

与现有逻辑的关系
不会替换原有在线数据源逻辑
如果当前剧集没有本地绑定,则行为与现有版本一致
如果本地绑定文件已经不存在,则本地媒体源不会返回可播放结果
本地文件只是作为一个额外的、可选择的特殊媒体源接入现有流程

已知限制
需要用户自己为每个新视频绑定本地视频,不过如果后续可以考虑增加自动绑定功能

潜在风险

  1. 绑定错误视频可能影响弹幕匹配与时间轴对齐
    弹幕加载流程会使用当前选中视频的一些信息参与匹配,包括文件名、文件大小以及视频时长。
    因此,如果用户为某一集绑定了错误的视频,尤其是在视频时长与实际剧集差异较大时,可能出现弹幕匹配结果不准确
    只能通过手动时间校准部分修正,无法完全解决“不同剪辑版本”带来的时间轴差异

当前实现没有针对“错误绑定但文件本身合法”的场景做额外阻止,因为从程序角度它仍然是一个有效的本地媒体文件。

  1. 自动相关功能会基于当前实际播放文件的时长工作
    例如:自动标记已观看、自动连播下一集、播放进度记忆与恢复
    这些逻辑依赖播放器当前返回的视频时长。
    因此,如果用户绑定的是错误视频,且视频长度明显不同于目标剧集,可能导致:
    自动标记已观看触发时机不准确
    自动切换下一集的时机不准确
    播放进度恢复行为基于错误文件长度工作
    从当前测试看,这类情况更可能表现为逻辑偏差,而不是直接导致程序崩溃。

  2. 当前没有对“错误绑定”做内容级校验
    这意味着功能设计上默认允许用户显式绑定任意本地视频文件;相应地,错误绑定带来的行为偏差也需要由用户自行承担。

验证情况
我在Windows平台上进行了编译,并绑定了本地视频进行观看,目前弹幕等都是正常的,但是无法验证来自弹弹play是否正常,不过从代码的角度是看应该没有问题

后续可选优化
如果这个方向可以接受,后续我认为还可以继续补充:
在本地绑定文件失效时提供更明确的 UI 提示
在恢复播放进度时增加更严格的边界保护
增加自动绑定等自动化功能
增加对Android平台的支持

@StageGuard
Copy link
Copy Markdown
Member

Please fix compilation errors and add unit test.

@StageGuard
Copy link
Copy Markdown
Member

StageGuard commented Apr 16, 2026

现在的实现并不是很好,本地导入的视频应该也接入 MediaCacheStorage,在缓存页显示。目前已有 TorrentMediaCacheStorage 和 HttpMediaCacheStorage,可以新建一个 LocalImportMediaCacheStorage (或者按照你 PR 里的名字延申的 LocalBindingMediaCacheStorage) 来存储本地的导入的视频信息,这样比较方便统一管理,还可以在启动时检测有效性。

@diary112233
Copy link
Copy Markdown
Author

收到,感谢您的建议。
我这边重新测试后发现目前这个实现确实还不够好,也还有一些问题,比如从网络源切到本地绑定视频时播放器会卡住。
我会继续修改,按这个方向再整理实现

@diary112233
Copy link
Copy Markdown
Author

还有一点,我想实现的播放本地视频的功能并不是想将本地的视频导入,而是想可以将U盘或移动硬盘上的视频直接播放,不将视频复制到缓存目录,这也是我一开始没有直接用现有的逻辑的原因。如果您有什么建议可以告诉我

@StageGuard
Copy link
Copy Markdown
Member

StageGuard commented Apr 17, 2026

还有一点,我想实现的播放本地视频的功能并不是想将本地的视频导入,而是想可以将U盘或移动硬盘上的视频直接播放,不将视频复制到缓存目录,这也是我一开始没有直接用现有的逻辑的原因。如果您有什么建议可以告诉我

并不是说要把视频复制到缓存目录,而是依旧沿用你实现的 binding 逻辑,只是将其接入 MediaCacheStorage 以方便管理元数据:接入后可以将删除 datastore 中数据的操作移动到缓存页,在缓存页中删除 binding 缓存可以只删除 datastore 中的元数据而不删除视频文件。

@diary112233
Copy link
Copy Markdown
Author

diary112233 commented Apr 17, 2026

我这边对实现做了一次比较大的调整,尝试复用现有的 cache request / storage / manager / selector 这一整套流程,而不是单独走一条新逻辑。当前实现不复制文件,只保存外部文件引用。同时也接入到了缓存目录的流程中。

不过现在还有一个比较严重的问题:在播放页面切换选集时,有较大概率会卡住。表现是进度条仍然在走,音频也正常播放,但画面和弹幕会卡住。看到 #2703 这个 issue,感觉可能和这个问题有关。如果是从缓存页面进入播放,就没有这个问题。
我另外有时从播放页切换也会出现整个程序无响应的情况,看起来和 #2678 有点类似。

由于这次实现改动比较大,我这边不太确定是继续在这个 PR 里迭代比较合适,还是再重新开一个 PR讨论,或者全部修改好了后再提交。想听一下您的建议。

@Him188
Copy link
Copy Markdown
Member

Him188 commented Apr 18, 2026

我感觉是没有必要做binding?一般也就播放一次吧。
我觉得支持在播放页面直接拖入视频文件来播放即可。
这个都没有必要走 media cache 链路,可以在拖拽后直接 mediaselector.select(new DefaultMedia(uri=local file)) 即可。select 可以传入任意 media,不需要是来自 fetcher 的

@diary112233
Copy link
Copy Markdown
Author

diary112233 commented Apr 18, 2026

我更倾向于做成按钮(我会将按钮放到二级菜单里的,不会让播放页面看起来更乱),和长期记忆。按钮可以更简单并且容易向手机平台拓展,长期记忆会在重新打开和二刷的时候更方便 issues #1888 这个也有人提到过。我所说的绑定逻辑主要是为了强调一集只能用一个缓存源,不然还要增加优先级的逻辑。至于接入缓存统一管理我也觉得是有价值的,不然从代码的角度上看总像是强行加上的一个功能,与现有的逻辑都不一致。不过不管怎么实现,都会出现我所说的从网络播放源切换到本地播放源的时候播放器卡住的情况,一直从根源上修不好,不过好像可以通过从代码上加一个重建一次播放器的步骤,就是相当于重新打开视频来解决这个问题。但是我觉得这不是长久化的解决方案。
屏幕截图 2026-04-18 181224
屏幕截图 2026-04-18 181303
image
这是我现在的接入缓存的实现,然后使用本地视频就不能再缓存了,防止有多个视频。这个pr我目前还没有提交,因为我上述的原因

@diary112233
Copy link
Copy Markdown
Author

diary112233 commented Apr 18, 2026

从网络播放源切换到本地播放源 和 切换使用本地缓存不同集 的时候播放器卡住的情况好像是由于调用VLC播放器太快导致的,我增加了400ms的延迟后,到目前为止还没有再次复现卡死问题。这个思路是参考的ZeBobo5/Vlc.DotNet#544 。此外,我也发现了使用bt缓存的时候也会有相同的情况,这可能是之前issue #2703 原因。如果没有新的情况出现的话,我近期应该会整理这些改动(包括使用本地缓存和解决播放器卡死的方法)到一个新的pr里。我先不关闭这个pr,如果您有其它建议,可以接着讨论。另外,目前这个延迟更像是一个 workaround,还不是最终修复方案,我也还在继续看看是否存在更好的同步处理方式。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants